use proc_macro::TokenStream;
use quote::{quote, ToTokens};
use syn::{parse_macro_input, Expr, ExprTuple, FnArg, Lit, ReturnType};

mod gen_rust_types;

#[proc_macro]
pub fn gen_rust_no_mangle(input: TokenStream) -> TokenStream {
    // Parse the input as an expression tuple
    let input = parse_macro_input!(input as ExprTuple);
    let elems = &input.elems;

    // Ensure we have exactly two elements in the tuple
    if elems.len() < 2 {
        return syn::Error::new_spanned(
            input,
            "Expected a tuple with two elements: (StructName, FilePath, Optioned value: bool is gen rust types)",
        )
        .to_compile_error()
        .into();
    }

    // Parse the first element as a path (struct name)
    let struct_name = match &elems[0] {
        Expr::Path(expr_path) => expr_path
            .path
            .get_ident()
            .cloned()
            .expect("Expected a valid identifier"),
        _ => {
            return syn::Error::new_spanned(&elems[0], "Expected a struct name")
                .to_compile_error()
                .into()
        }
    };

    // Parse the second element as a string literal (file path)
    let file_path = match &elems[1] {
        Expr::Lit(expr_lit) => match &expr_lit.lit {
            Lit::Str(lit_str) => lit_str.value(),
            _ => {
                return syn::Error::new_spanned(
                    &elems[1],
                    "Expected a string literal for the file path",
                )
                .to_compile_error()
                .into()
            }
        },
        _ => {
            return syn::Error::new_spanned(
                &elems[1],
                "Expected a string literal for the file path",
            )
            .to_compile_error()
            .into()
        }
    };

    // Parse the second element as a string literal (file path)
    let is_gen_rust_types = match &elems.get(2) {
        Some(v) => match v {
            Expr::Lit(expr_lit) => match &expr_lit.lit {
                Lit::Bool(v) => v.value(),
                _ => {
                    return syn::Error::new_spanned(&elems[1], "bool is gen rust types")
                        .to_compile_error()
                        .into()
                }
            },
            _ => {
                return syn::Error::new_spanned(&elems[1], "bool is gen rust types")
                    .to_compile_error()
                    .into()
            }
        },
        None => false,
    };

    // Read the file content
    let lib_rs_content = std::fs::read_to_string(&file_path).expect("Failed to read lib.rs file");

    // Parse the file content
    let file = syn::parse_file(&lib_rs_content).expect("Failed to parse file");

    // Extract #[no_mangle] functions
    let mut functions = vec![];
    let mut types = vec![];
    let mut is_gen_a = false;

    for item in file.items {
        if let syn::Item::Static(item) = item {
            // Check if the function is public
            match item.vis {
                syn::Visibility::Public(_) => {}
                _ => continue,
            };

            // Check if the function has #[no_mangle] attribute
            let has_no_mangle = item
                .attrs
                .iter()
                .any(|attr| attr.to_token_stream().to_string().contains("no_mangle"));
            if !has_no_mangle {
                continue;
            }

            #[allow(unused_mut)]
            let mut static_type = Some(&item.ty);
            let mut_val = item.mutability.clone();

            is_gen_a = true;
            let mut_quote = match mut_val {
                _ if static_type.to_token_stream().to_string().replace(' ', "").contains("&str") => {
                    // static_type = None;
                    // quote! { &'a str }
                    // not support static &str
                    continue;
                }
                syn::StaticMutability::Mut(_) => {
                    quote! { &'a mut }
                }
                syn::StaticMutability::None => {
                    quote! { &'a }
                }
                _ => {
                    // unknown static mutability
                    quote! { &'a }
                }
            };

            let static_name = &item.ident;
            

            functions.push(quote! {
                #static_name : #mut_quote #static_type,
            });
        } else if let syn::Item::Fn(item) = item {
            // Check if the function is public
            match item.vis {
                syn::Visibility::Public(_) => {}
                _ => continue,
            };

            // Check if the function has #[no_mangle] attribute
            let has_no_mangle = item
                .attrs
                .iter()
                .any(|attr| attr.to_token_stream().to_string().contains("no_mangle"));
            if !has_no_mangle {
                continue;
            }

            let fn_name = &item.sig.ident;

            // Process function arguments
            let inputs: Vec<_> = item
                .sig
                .inputs
                .iter()
                .map(|arg| {
                    if let FnArg::Typed(pat_type) = arg {
                        let name = if let syn::Pat::Ident(pat_ident) = &*pat_type.pat {
                            &pat_ident.ident
                        } else {
                            panic!("Unsupported function argument pattern")
                        };
                        let ty = &pat_type.ty;
                        quote! { #name: #ty }
                    } else {
                        panic!("Unsupported function argument pattern")
                    }
                })
                .collect();

            // Process return type
            let ret_type = match &item.sig.output {
                ReturnType::Type(_, ty) => quote! { -> #ty },
                ReturnType::Default => quote! {},
            };

            if ret_type.to_string().contains("'a") {
                is_gen_a = true;
            }

            functions.push(quote! {
                #fn_name: fn(#(#inputs),*) #ret_type,
            });
        } else if is_gen_rust_types {
            if let Some((_name, v)) = gen_rust_types::gen_rust_types(item) {
                types.push(v);
            }
        }
    }

    let gen_a = if is_gen_a {
        quote! { <'a> }
    } else {
        quote! {}
    };

    // Generate the struct code
    let expanded = quote! {
        #(#types)*

        #[derive(dlopen2::wrapper::WrapperApi)]
        pub struct #struct_name #gen_a {
            #(#functions)*
        }
    };

    println!("hotload_macro gen code:\n{}\n", expanded);

    // Return the generated code as TokenStream
    TokenStream::from(expanded)
}
